function [ totalTime ] = Net_simulateQueuingNetwork(taskExecOrder, taskList )
%%Funcja wyznacza czas wykonywania sie wszystkich zadan w sieci kolejkowej

%%
%Utworzenie trojwymiarowej macierzy zadan ulozonych wedlug kolejnosci
%wykonywania
sortedTaskList = zeros(size(taskList,1), size(taskList,2), size(taskList,3));
for index=1:length(taskExecOrder)
    sortedTaskList(:,:,index) = taskList(:,:,taskExecOrder(index));
end

totalTime = 0;
stillExecutedTasks = zeros(size(taskList,3),size(taskList,1));

%%
%Petla wykonuje sie do momentu kiedy ktorekolwiek z zadan jeszcze sie nie
%skonczylo
while tasksAlreadyPending(sortedTaskList)
    executionMatrix = getExecutionMatrix(sortedTaskList);
    pendingTaskNr = getNumberOfPendingTasks(executionMatrix);
    
    executionMatrix = removeNewTasksFromStillBusyMachines(executionMatrix,stillExecutedTasks);
    
    iterationsOnMachines = getIterationsForEveryMachine(executionMatrix);
    minStep = getMinimalIterationStep(iterationsOnMachines);
    totalTime = totalTime + minStep*pendingTaskNr;
    
    executedTasksInIteration = getTasksNumberExecutedInIteration(executionMatrix);

    sortedTaskList = updateSortedTaskList(sortedTaskList,executedTasksInIteration,minStep);
    stillExecutedTasks = upadateExecutionMatrix(executionMatrix,executedTasksInIteration, minStep);
end
return;
%%Funkcja sprawdzajaca czy sa jeszcze zadania oczekujace na wykonanie
    function pending = tasksAlreadyPending(taskList)
       pending = false;
       for taskNr=1:size(taskList,3)
           if  any(any(taskList(:,:,taskNr)));
               pending = true; 
               break;
           end
       end
    end
%% Macierz opisujaca na ktorej ktore zadanie na ktorej maszynie bedzie wykonywne
% Macierz rowna [0 6 0 0; 0 5 0 0; 0 0 0 7] oznacze ze najpierw zadanie 1 bedzie
% wykonywane na maszynie 2 przez 6 [j], zadanie 2 na maszynie 2 przez 5[j]
% a zadanie 3 na maszynie 4 przez 7 jednostek
    function executionMatrix = getExecutionMatrix(sortedTaskList)
        executionMatrix = zeros(size(sortedTaskList,3),size(sortedTaskList,2));
        for i=1:size(sortedTaskList,3)   
            for j=1:size(sortedTaskList,2)
                if any(sortedTaskList(j,:,i))
                    executionMatrix(i,:) = sortedTaskList(j,:,i);
                    break;
                end
            end
        end        
    end
%%
%Funckja usuwa z tablicy wykonywanych zadan nowo dodane zadania, jesli na
%danej maszynie jakies inne zadanie jest juz wykonywane. Zapobiega to
%wywlaszczaniu wykonywanych zadan.
    function executionMatrix = removeNewTasksFromStillBusyMachines(executionMatrix,stillExecutedTasks)
          for i=1:size(executionMatrix,2)
            if any(stillExecutedTasks(:,i))
                executionMatrix(:,i) = stillExecutedTasks(:,i);
            end
          end
    end
%%
%Funkcja wyznaczajaca czasy jakie zadania chca wykonywac sie na
%poszczegolnych maszynach, np: [5 6] oznacza ze jakies zadanie chce byc
%wykonywane 5[j] na maszynie 1, a inne 6[j] na maszynie 2.
    function iterationsOnMachines = getIterationsForEveryMachine(executionMatrix)
       [~, jt, vt] = find(executionMatrix);
       t = logical(diff([0;jt]));
       iterationsOnMachines = NaN([size(executionMatrix,2) 1]);
       iterationsOnMachines(jt(t)) = vt(t);
       iterationsOnMachines(isnan(iterationsOnMachines)) = 0;
    end
%%
%Funcja zwraca numery zadan ktore beda mogly byc wykonywane w kolejnej
%iteracji. 
    function executedTasksInIteration = getTasksNumberExecutedInIteration(executionMatrix)
         for i=1:size(executionMatrix,2)
            idx = find(executionMatrix(:,i) > 0,1);
            if idx < size(executionMatrix,1)
                executionMatrix(idx+1:size(executionMatrix,1),i) = 0;
            end
        end
        [it, ~, ~] = find(executionMatrix);
        executedTasksInIteration = it; 
    end
%%
%Funcja zwraca minimalny czas jaki nastapi przed kolejna iteracja. Jest
%najkrotszy czas jaki pozostal zadaniu do ukonczenia pracy na maszynie
    function minStep = getMinimalIterationStep(iterationsOnMachines)
        tmp = iterationsOnMachines;
        tmp(tmp==0) = Inf;
        minStep = min(tmp);
    end
%%
%Funkcja pomniejsza czas jaki pozostal na ukonczenie pracy na maszynie dla
%zadan aktualnie sie wykonujacych
    function sortedTaskList = updateSortedTaskList(sortedTaskList,executedTasksInIteration,minStep)
        for i=1:length(executedTasksInIteration)
            for j=1:size(sortedTaskList,2)
                if any(sortedTaskList(j,:,executedTasksInIteration(i)))
                    nonZeroIndex = find(sortedTaskList(j,:,executedTasksInIteration(i))>0,1);
                    sortedTaskList(j,nonZeroIndex,executedTasksInIteration(i)) =  sortedTaskList(j,nonZeroIndex,executedTasksInIteration(i))- minStep;
                    break;
                end
            end
        end
    end
%%
    function  stillExecutedTasks = upadateExecutionMatrix(executionMatrix,executedTasksInIteration, minStep)
        stillExecutedTasks = executionMatrix;
        for i=1:length(executedTasksInIteration)
            nonZeroIndex = find(stillExecutedTasks(executedTasksInIteration(i),:));
            stillExecutedTasks(executedTasksInIteration(i),nonZeroIndex) = stillExecutedTasks(executedTasksInIteration(i),nonZeroIndex)-minStep;
        end
    end
%%
%Funkcja zwraca ilosc zadan wykonujacych sie badz oczekujacych, aby mozliwe
%bylo zwiekszenie wartosci funkcji celu
    function nrOfPendingTasks = getNumberOfPendingTasks(executionMatrix)
        nrOfPendingTasks = 0;
        for i=1:size(executionMatrix,1)
            if any(executionMatrix(i,:))
                nrOfPendingTasks = nrOfPendingTasks + 1;
            end
        end
    end
end